<template>
  <div class="roles_conatainer">
    <!-- 面包屑导航 -->
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>权限管理</el-breadcrumb-item>
      <el-breadcrumb-item>角色列表</el-breadcrumb-item>
    </el-breadcrumb>
    <!-- 卡片视图区域 -->
    <el-card class="box-card">
      <!--添加角色按钮，将其置于layout布局el-row中，固定其位置在左侧 -->
      <!---添加角色按钮，绑定click事件，使得 dialogVisible: true-->
      <el-row>
        <el-col :span="1">
          <el-button type="primary" @click="showDialog">添加角色</el-button>
        </el-col>
      </el-row>
      <!-- 用户列表区 -->
      <el-table :data="rolesList" style="width: 100%" class="roles_table" border stripe>
        <!--展开列-->
        <el-table-column type="expand">
          <!--通过作用域插槽拿到该行数据-->
          <template slot-scope="_role">
            <!-- {{ _role.row }} -->
            <!--使用栅格布局el-row-->
            <el-row v-for="(item1, i1) in _role.row.children" :key="item1.id" :class="['expand-row', 'border_bot', i1 === 0 ? 'border_top' : '']" type="flex" align="middle">
              <!--存放一级权限的标题，占总宽度的5/24-->
              <el-col :span="5">
                <el-tag @close="removeRightsById(_role.row, item1.id)" closable>{{ item1.authName }}</el-tag>
                <i class="el-icon-caret-right"></i>
              </el-col>
              <!--存放二级和三级权限-->
              <el-col :span="19">
                <el-row v-for="(item2, i2) in item1.children" :key="item2.id" :class="['border_bot', i2 === 0 ? 'border_top' : '']" type="flex" align="middle">
                  <el-col :span="6">
                    <el-tag type="success" @close="removeRightsById(_role.row, item2.id)" closable> {{ item2.authName }}</el-tag>
                    <i class="el-icon-caret-right"></i>
                  </el-col>
                  <el-col :span="18">
                    <el-tag type="warning" v-for="item3 in item2.children" :key="item3.id" @close="removeRightsById(_role.row, item3.id)" closable>
                      {{ item3.authName }}
                    </el-tag>
                  </el-col>
                </el-row>
              </el-col>
            </el-row>
          </template>
        </el-table-column>
        <!--type="index"就为表格加了索引列，默认从1开始，就是给表格每个tr行在第一个td位置加上连续序号-->
        <el-table-column type="index" label="#"></el-table-column>
        <el-table-column prop="roleName" label="角色名称"> </el-table-column>
        <el-table-column prop="roleDesc" label="角色描述"> </el-table-column>
        <el-table-column label="操作" class="roles_item" width="340px">
          <!--自定义插槽-->
          <template slot-scope="_item">
            <!--修改按钮-点击该按钮，弹出修改对话框。对话框中渲染出用户名、邮箱、手机号信息，但是用户名是不能更改的-->
            <el-button type="primary" icon="el-icon-edit" size="mini" @click="shiftRoleDialog(_item.row.id)">编辑</el-button>
            <!--删除按钮-->
            <el-button type="danger" icon="el-icon-delete" size="mini" @click="deleteRoleItem(_item.row.id)">删除</el-button>
            <!--分配权限 需求：分配权限图标不易作出识别，可以给其加一个提示，Tooltip文字组件，但是鼠标进入提示文本区域，提示不会隐藏
              重置enterable属性为false-->
            <el-tooltip class="item" effect="dark" content="分配权限" placement="top" :enterable="false">
              <el-button type="warning" icon="el-icon-setting" size="mini" @click="showSetRightDialog(_item.row)">分配权限 </el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <!--点击【添加角色】按钮弹出的对话框-->
      <el-dialog title="添加角色" :visible.sync="dialogRolesVisible" width="50%" @close="addDialogClose">
        <span>
          <!--填写表单时，如果未完成或者合法完成表单内容填写，就关闭了对话框。对话框
      下次开启时，若希望得到一个全新的表单（清除上次残存的表单内容和填写验证状态提示），
      就使用form表单原生对象的 resetFields()方法，重置表单域-->
          <el-form :model="roleForm" :rules="rules" ref="roleForm" label-width="100px">
            <el-form-item label="角色名称" prop="roleName">
              <el-input v-model="roleForm.roleName"></el-input>
            </el-form-item>
            <el-form-item label="角色描述" prop="roleDesc">
              <el-input v-model="roleForm.roleDesc"></el-input>
            </el-form-item>
          </el-form>
        </span>
        <span slot="footer" class="dialog-footer">
          <el-button @click="dialogRolesVisible = false">取 消</el-button>
          <!--点击【确定】按钮，不是直接dialogRolesVisible = false，关闭对话框，
        而是要使用原生的validate()方法进行预校验-->
          <el-button type="primary" @click="addRolesPre">确 定</el-button>
        </span>
      </el-dialog>
      <!--编辑提交角色弹出的对话框-->
      <el-dialog title="修改角色" :visible.sync="editDialogRolesVisible" width="50%" @close="editAddDialogClose">
        <span>
          <!--填写表单时，如果未完成或者合法完成表单内容填写，就关闭了对话框。对话框
      下次开启时，若希望得到一个全新的表单（清除上次残存的表单内容和填写验证状态提示），
      就使用form表单原生对象的 resetFields()方法，重置表单域-->
          <el-form :model="editRoleForm" :rules="rules" ref="editRoleForm" label-width="100px">
            <el-form-item label="角色名称" prop="roleName">
              <el-input v-model="editRoleForm.roleName"></el-input>
            </el-form-item>
            <el-form-item label="角色描述" prop="roleDesc">
              <el-input v-model="editRoleForm.roleDesc"></el-input>
            </el-form-item>
          </el-form>
        </span>
        <span slot="footer" class="dialog-footer">
          <el-button @click="editDialogRolesVisible = false">取 消</el-button>
          <!--点击【确定】按钮，不是直接editDialogRolesVisible = false，关闭对话框，
        而是要使用原生的validate()方法进行预校验-->
          <el-button type="primary" @click="editRolePre">确 定</el-button>
        </span>
      </el-dialog>
      <!--点击【分配权限】按钮，弹出的对话框-->
      <el-dialog title="分配权限" :visible.sync="setRightDialogVisible" width="50%" @close="setRightDialogClosed">
        <!--树形控件 Tree show-checkbox是否可选择  default-expand-all是否打开时所有节点全部展开
        两种方式获取半选中节点和选中的叶子节点：node方式或者key方式(其中key方式必须要声明node-key属性)。如使用key方式，需要声明node-key="id"
        这个id名称，可从响应的请求数据中获悉-->
        <el-tree :data="rightList" :props="defaultProps" node-key="id" :default-checked-keys="defaultKeys" ref="treeRef" show-checkbox default-expand-all> </el-tree>
        <span slot="footer" class="dialog-footer">
          <el-button @click="setRightDialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="setRightDialogPre">确 定</el-button>
        </span>
      </el-dialog>
    </el-card>
    <!-- <h3>这是Roles子组件</h3> -->
  </div>
</template>

<script>
export default {
  created() {
    this.getRolesList();
  },
  data() {
    return {
      // 存放角色列表数组
      rolesList: [],
      // 存放权限列表数据
      rightList: [],
      // 添加角色对话框状态码
      dialogRolesVisible: false,
      // 编辑角色信息弹出的对话框状态码
      editDialogRolesVisible: false,
      // 点击【分配权限】按钮，弹出的对话框
      setRightDialogVisible: false,
      // 添加数据对象
      roleForm: {},
      // 编辑角色信息数据对象
      editRoleForm: {},
      // 表单添加角色规则
      rules: {
        roleName: [
          { required: true, message: '请输入角色名称', trigger: 'blur' },
          { min: 2, max: 16, message: '长度在 3 到 20 个字符', trigger: 'blur' }
        ],
        roleDesc: [
          { required: true, message: '请输入角色描述', trigger: 'blur' },
          { min: 2, max: 16, message: '长度在 3 到 20 个字符', trigger: 'blur' }
        ]
      },
      // 树形控件的绑定对象
      defaultProps: {
        // children和label是Tree控件默认属性名
        // children父子节点的名称
        children: 'children',
        // label属性值是要显示的变量
        label: 'authName'
      },
      // 绑定到el-tree的default-checked-keys属性，存储被选中节点的id值，
      defaultKeys: [],
      // 当前即将分配权限的角色id
      rId: ''
    };
  },
  methods: {
    // 获取角色列表
    async getRolesList() {
      const { data: res } = await this.$http.get('roles');
      console.log(res);
      // 获取角色列表失败提示
      if (res.meta.status !== 200) this.$message.error(res.meta.msg);
      this.rolesList = res.data;
      // 获取角色列表成功提示
      this.$message.success(res.meta.msg);
    },
    // 【添加角色】按钮点击，弹出对话框
    showDialog() {
      this.dialogRolesVisible = true;
    },
    // 对话框关闭的close事件
    addDialogClose() {
      // 重置表单域
      this.$refs.roleForm.resetFields();
    },
    // 添加对话框的预校验，校验成功就发起数据请求
    addRolesPre() {
      this.$refs.roleForm.validate(async valid => {
        // console.log(valid);
        if (!valid) return;
        // 校验成功后，开始提交数据
        const { data: res } = await this.$http.post('roles', this.roleForm);
        console.log(res);
        // 请求提交数据失败
        if (res.meta.status !== 201) return this.$message.error(res.meta.msg);
        // 2.请求数据成功
        // 2.1 添加角色成功后的提示信息
        this.$message.success(res.meta.msg);
        // 2.2 关闭对话框
        this.dialogRolesVisible = false;
        // 2.3 重新请求角色列表数据
        // 重新调用获取角色列表数据，更新了角色列表数组rolesList
        this.getRolesList();
      });
    },
    // 【编辑角色】按钮点击时，弹出对话框
    async shiftRoleDialog(id) {
      // 当前所在行数据的id
      // 根据id请求数据，将数据渲染在对话框上
      const { data: res } = await this.$http.get('roles/' + id);
      console.log(res);
      this.editRoleForm = res.data;
      // 弹出对话框
      this.editDialogRolesVisible = true;
    },
    // 关闭修改角色弹出的对话框时，需要重置表单域
    editAddDialogClose() {
      this.$refs.editRoleForm.resetFields();
    },
    // 修改角色信息的预校验，校验成功后，请求更改
    editRolePre() {
      this.$refs.editRoleForm.validate(async valid => {
        // console.log(valid);
        if (!valid) return;
        // 预校验成功，开始提交更改
        const { data: res } = await this.$http.put(`roles/${this.editRoleForm.roleId}`, {
          // 注：此处不能直接传入this.editRoleForm对象，原因是：更改角色请求参数是roleName和roleDesc。然而，
          // this.editRoleForm还包含roleId等其他属性
          roleName: this.editRoleForm.roleName,
          roleDesc: this.editRoleForm.roleDesc
        });
        if (res.meta.status !== 200) return this.$message.error(res.meta.msg);
        // 2. 提交更改成功后
        // 2.1 提示成功信息
        this.$message.success('角色信息更改成功！');
        // 2.2 关闭对话框
        this.editDialogRolesVisible = false;
        // 2.3 更新下数据
        this.getRolesList();
      });
    },
    // 删除当前角色信息条目，并在删除前给响应的提示
    async deleteRoleItem(id) {
      const result = await this.$messageBox
        .confirm('此操作将永久删除该文件, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
        .catch(error => error);
      console.log(result);
      // 1.当result值为false,表示点击了【取消】，此时给一个取消删除的提示信息即可，不提交删除请求
      if (result !== 'confirm') return this.$message.error('删除请求已取消！');
      // 2.当result为true,表示点击了【确定】按钮。
      // 2.1首先提交删除请求
      const { data: res } = await this.$http.delete(`roles/${id}`);
      console.log(res);
      if (res.meta.status !== 200) {
        return this.$message.error('删除请求提交失败！');
      }
      // 2.2更新数据
      this.getRolesList();
      // 2.3给一个删除成功的提示
      this.$message.success('删除该条角色信息成功！');
    },
    // 根据id删除对应的权限-弹框提示是否删除？
    // role是作用域插槽对象---拿到的是被操作的那一行数据
    async removeRightsById(role, rightId) {
      // 弹框提示是否删除？messageBox组件
      const result = await this.$messageBox
        .confirm('此操作将永久删除该文件, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
        .catch(error => error);
      // 1.result值为cancel,表示点击了取消按钮，此时只需给出提示“取消删除操作”即可
      if (result !== 'confirm') {
        return this.$message.error('删除权限操作已取消！');
      }
      // 2.代码继续运行，表示点击的是【确定】按钮
      // 2.1 提交删除请求
      const { data: res } = await this.$http.delete(`roles/${role.id}/rights/${rightId}`);
      console.log(res);
      // 当rightId是三级权限的id时，请求结果就是三级权限数组，res.data可以拿到这个数组，role是该行的所有数组对象，role.children拿到的三级权限数组
      // 二级权限和一级权限也是一一对应
      if (res.meta.status !== 200) {
        return this.$message.error('删除权限操作失败!');
      }
      // 2.2 删除操作成功-从数据库中成功删除了,重新请求获取角色页面
      /*
      1.不建议直接调用getRolesList()函数重新渲染整个页面，而且删除后权限折叠消失。注意：当点击【确定】按钮后，
      请求得到的数据，就是最新的数据。直接将res.data的值赋给role对象，让其工作
      在生命周期的更新阶段
      2.注意：role.children为第一级节点下的children。比如；删除了二级或者三级节点后台响应的数据res.data也同步返回这种变化
      规律：1-2-3级权限，删除了1级权限，2,3级权限节点也将同时移除。同理：删除二级权限，则该二级权限和它包含的
      三级权限节点全部移除
       */
      // this.getRolesList();
      role.children = res.data;
      // 2.3 删除权限操作成功情况下的提示
      this.$message.success('已经成功删除该权限!');
    },
    // 【分配权限】按钮点击事件
    /*
      分配权限的过程：
      1.点击【分配权限】按钮，首先树形结构的所有节点全部展开，方便选择操作
      2.树形结构中所有已经被选中的节点，在结构中呈“选中”状态，default-checked-keys属性控制
       这需要点击该按钮时，把所有在折叠框中显示的三级权限id全部存储在default-checked-keys
       属性绑定的数组defaultKeys中
     */
    async showSetRightDialog(role) {
      console.log(role);
      // 对话框显示出来之前，将权限管理的数据以树形结构显示出来
      const { data: res } = await this.$http.get('rights/tree');
      console.log(res);
      if (res.meta.status !== 200) {
        return this.$message.error('获取权限列表数据失败!');
      }
      // 2.树形数据对象请求成功后
      // 2.1 获取权限列表成功，存放于数组rightList
      this.rightList = res.data;
      // 保存角色id
      this.rId = role.id;
      this.$message.success('获取权限列表数据成功!');
      // 调用递归函数把当前角色下所有三级节点的id，存放在数组defaultKeys中
      /*
      bug:当点击第一行的【权限管理】按钮，显示第一行中所有被勾选的三级权限，
      当点击第二行的【权限管理】按钮，显示的被勾选的权限是第1行三级权限和第2行的三级权限。
      原因是：数组defaultKeys把第二行的三级权限id继续push到了数组defaultKeys中了
      为此，需要每次关闭对话框，点击其他行的【分配权限】按钮时，将数组defaultKeys元素清空，
      所以需要在对话框的关闭(close)事件中处理
       */
      // 遍历后，拿到所有三级权限id组成的数组，该值动态绑定给default-checked-keys
      this.getLeafKeys(role, this.defaultKeys);
      // 弹出对话框
      this.setRightDialogVisible = true;
    },
    setRightDialogClosed() {
      console.log(this.defaultKeys);
      this.defaultKeys = [];
    },
    /*
      定义一个递归函数getLeafKeys
      参数1：node 节点，要判别它是否为三级节点
      参数2：数组
      通过递归函数的形式，获取所有的角色下面的三级权限的id值，并保存在
      数组defaultKeys中
     */
    getLeafKeys(node, arr) {
      // 三级节点没有children属性，据此判断是否为三级节点
      if (!node.children) {
        // 是三级节点，该节点id值保存到数组中
        return arr.push(node.id);
      }
      // if语句外面不是三级节点的情形：可能为二级或者三级节点，此时
      // 使用递归进一步确认
      node.children.forEach(item => {
        this.getLeafKeys(item, arr);
      });
    },
    // 【确定】按钮的点击事件
    /*
    1.根据数据接口文档，提交自己分配的权限，需要所有选中权限的叶子节点、半选节点的key
    2.Tree控件提供了原生的方法getCheckedKeys（）获取被选中的叶子节点的key组成的数组
    getHalfCheckedKeys()获取半选节点组成的key
     */
    async setRightDialogPre() {
      // 测试代码 商品管理(权限id:101)-商品列表(权限id：104)-添加商品（选中叶子节点id:105）
      console.log(this.$refs.treeRef.getCheckedKeys()); // [105] 三级权限,为选中叶子节点
      console.log(this.$refs.treeRef.getHalfCheckedKeys()); // [101,104] [一级权限，二级权限] 为半选中节点
      /* 注意：ES6新语法，展开运算符 ... ，类似Array的concat方法 */
      const keys = [...this.$refs.treeRef.getCheckedKeys(), ...this.$refs.treeRef.getHalfCheckedKeys()];
      // const keys = [
      //   this.$refs.treeRef
      //     .getHalfCheckedKeys()
      //     .concat(this.$refs.treeRef.getCheckedKeys())
      // ];
      console.log(keys);
      // Array的join()方法将数组元素拼接起来
      const idStr = keys.join(',');
      // 分配权限完成后-发起提交请求
      // id值应该在弹出对话框的时候保存data属性中，方便此处取用
      const { data: res } = await this.$http.post(`roles/${this.rId}/rights`, {
        rids: idStr
      });
      console.log(res);
      if (res.meta.status !== 200) {
        return this.$message.error('分配权限请求失败!');
      }
      // 2.分配请求成功
      // 2.1 重新刷新页面
      this.getRolesList();
      // 2.2 成功提示
      this.$message.success('分配权限操作已成功!');
      // 2.3 关闭对话框
      this.setRightDialogVisible = false;
    }
    // 展开树形结构节点
    // el-tree绑定事件@node-click="handleNodeClick"
    // handleNodeClick(data) {
    //   console.log(data);
    // }
  }
};
</script>

<style lang="less" scoped>
.roles_conatainer {
  /* table操作框内放在公共样式global.less中复用 */
  /* 为一级权限，二级权限展开列设置样式 */
  .expand-row {
    .el-col {
      .el-tag {
        margin: 7px !important;
      }
    }
  }
  .border_bot {
    border-bottom: 1px solid #eee;
  }
  .border_top {
    border-top: 1px solid #eee;
  }
}
</style>
